package com.beta.auth.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.beta.auth.config.UserAuthCfgBean;
import com.beta.auth.constants.AuthChannel;
import com.beta.auth.constants.BusinessConstants;
import com.beta.auth.constants.CacheConstans;
import com.beta.auth.dto.auth.*;
import com.beta.auth.service.AbstractAuthService;
import com.beta.auth.service.SelfAppAuthService;
import com.beta.auth.utils.HttpService;
import com.beta.auth.utils.JwtUtil;
import com.beta.auth.utils.ResponseUtils;
import com.beta.auth.utils.WXUtils;
import com.beta.auth.utils.aes.SHA1;
import com.beta.cat.service.RedissonService;
import com.beta.cat.utils.AESUtil;
import com.beta.cat.utils.JsonUtil;
import com.beta.user.auth.center.api.constant.UserAuthConstant;
import com.beta.user.auth.center.api.model.SelfappAuthAcc;
import com.beta.user.auth.center.api.request.AddSelfAppAuthAccReq;
import com.beta.user.auth.center.api.request.QuerySelfAppAuthAccReq;
import com.beta.user.auth.center.api.request.UpdateSelfAppAuthAccReq;
import com.beta.user.auth.center.api.response.AddSelfAppAuthAccResp;
import com.beta.user.auth.center.api.response.AuthCorpInfoResp;
import com.beta.user.auth.center.api.response.QuerySelfAppAuthAccResp;
import com.beta.user.auth.center.api.response.UpdateSelfAppAuthAccResp;
import com.beta.user.auth.center.api.service.IRPCAuthCorpInfoervice;
import com.beta.user.auth.center.api.service.IRPCSelfAppAuthAccService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 自建应用授权实现
 */
@Slf4j
@Service
public class SelfAppAuthServiceImpl extends AbstractAuthService implements SelfAppAuthService {

    /**
     * 腾讯自建应用基础（静默）授权传参
     */
    private static final String SCOPE_BASE = "snsapi_base";

    /**
     * 腾讯自建应用高级（手动）授权传参
     */
    private static final String SCOPE_PRIVATE = "snsapi_privateinfo";

    private static final String OPEN_ID = "openid";

    @Autowired
    private UserAuthCfgBean userAuthCfgBean;

    @Autowired
    private RedissonService redissonService;

    @Autowired
    private HttpService httpService;

    @DubboReference
    private IRPCSelfAppAuthAccService rpcAuthAccService;

    @DubboReference
    private IRPCAuthCorpInfoervice authCorpInfoervice;


    @Override
    public String getChannel() {
        return AuthChannel.SELF_APP.getChannel();
    }

    private String appendUrlParam(String urlString,String paramName, String paramValue){
        try {
            URL url = new URL(urlString);
            String query = url.getQuery();

            if (query == null || query.isEmpty()) {
                // 如果 URL 中不存在查询参数，则添加 sTime 参数
                urlString += "?" + paramName + "=" + URLEncoder.encode(paramValue, "UTF-8");
            } else {
                // 如果 URL 中存在查询参数，则判断是否存在 sTime 参数
                if (query.contains(paramName)) {
                    // 如果存在 sTime 参数，则替换其值
                    String[] params = query.split("&");
                    for (int i = 0; i < params.length; i++) {
                        if (params[i].startsWith(paramName + "=")) {
                            params[i] = paramName + "=" + URLEncoder.encode(paramValue, "UTF-8");
                            break;
                        }
                    }
                    urlString = urlString.replace(query, String.join("&", params));
                } else {
                    // 如果不存在 sTime 参数，则在现有参数后添加 sTime 参数
                    urlString += "&" + paramName + "=" + URLEncoder.encode(paramValue, "UTF-8");
                }
            }
            return urlString;
        } catch (Exception e) {
            log.error("appendUrlParam error:{}",e);
        }
        return urlString;
    }

    @Override
    public void invokeAuth(AuthDto authDto, WXAuthState authState, HttpServletRequest request, HttpServletResponse response) {
        try {
            // 1.校验参数合法性
            boolean result = super.checkAuthCode(authState, authDto, response);
            if (authDto.getAc() == -1) {
                return;
            }

            String redirectUrl;
            String authType = authState.getAuthType();
            if (!result) {
                // 2.构造企业微信授权链接
                redirectUrl = getSelfAppAuthUrl(authState);
            } else {
                AtomicLong bjwte = new AtomicLong(0);
                AtomicInteger hasPrivate = new AtomicInteger(UserAuthConstant.SELF_APP_AUTH_TYPE_USER_INFO.equals(authType) ? UserAuthConstant.ADVANCED_AUTH_FLAG : UserAuthConstant.BASE_AUTH_FLAG);

                String code = authDto.getCode();
                GetUserInfoDto userInfo = this.getUserInfo(code, false);
                log.warn("userInfo :{}", JsonUtil.toJson(userInfo));
                if (userInfo == null || StringUtils.isEmpty(userInfo.getUserId())) {
                    log.warn("获取到用户基本信息不合法，userInfo = {}，authDto = {}", JSON.toJSONString(userInfo), JSON.toJSONString(authDto));
                    ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.WX_ERROR_CODE, response);
                    return;
                }

                String userId = userInfo.getUserId();
                String corpId = StringUtils.EMPTY;
                if (userId.contains("/")) {
                    corpId = userId.split("/")[0];

                }
                String userTicket = userInfo.getUserTicket();

                Map<String, String> authAccount = new HashMap<>();
                long ct = System.currentTimeMillis();
                authAccount.put(CT, String.valueOf(ct));
                authAccount.put(USERID, userId);

                // 3.识别是基础授权还是高级授权
                if (StringUtils.isEmpty(authType) || StringUtils.equals(authType, UserAuthConstant.SELF_APP_AUTH_TYPE_BASE)) {
                    // 基础授权相关操作
                    if (!invokeBaseAuth(userId, authAccount, bjwte, hasPrivate)) {
                        ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.RPC_ERROR_CODE, response);
                        return;
                    }
                } else if (StringUtils.equals(authType, UserAuthConstant.SELF_APP_AUTH_TYPE_USER_INFO)) {
                    // 高级授权相关操作
                    if (!invokeUserInfoAuth(userId, userTicket, authAccount, bjwte)) {
                        ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.RPC_ERROR_CODE, response);
                        return;
                    }
                } else {
                    log.warn("暂不支持的授权类型，authType = {}", authType);
                    ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.PARAMS_INVALID_CODE, response);
                    return;
                }

                // 功能类似于 uparms ，用于标识资源的归属者
                Map<String, String> signet = new HashMap<>(2);
                signet.put(USERID, userId);
                signet.put(CT, String.valueOf(ct));

                BetaSdkDto betaSdkDto = this.getBetaSdkDto(authDto.getReturnUrl(), corpId);
                if (Objects.isNull(betaSdkDto)) {
                    ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.WX_ERROR_CODE, response);
                    return;
                }

                String bjwt = JwtUtil.encrypt(authAccount, userAuthCfgBean.getJwtSecret(), bjwte.get() - ct);
                redirectUrl = authState.getReturnUrl();
                log.info("authState.getReturnUrl()={}", authState.getReturnUrl());
                redirectUrl = appendUrlParam(redirectUrl,"sTime",String.valueOf(System.currentTimeMillis()));
                if (StringUtils.isNotEmpty(corpId)) {
                    redirectUrl += redirectUrl.contains("?") ? "&" : "?";
                    redirectUrl += "corpid=" + corpId;
                }
                redirectUrl += "#"
                        + "wxsdk=" + URLEncoder.encode(JSON.toJSONString(betaSdkDto), "UTF-8")
                        + "&ustate=" + authState.getUstate()
                        + "&bjwt=" + bjwt
                        + "&bjwte=" + bjwte.get()
                        + "&hasPrivate=" + hasPrivate.get()
                        + "&signet=" + AESUtil.encrypt(JSON.toJSONString(signet), userAuthCfgBean.getAuthAesKey());
                /**
                 * 将bjwt会话有效期放到缓存中，有效期默认半小时，网关侧可以基于该缓存做会话有效期校验
                 */
                String cacheKey = String.format(CacheConstans.BJWT_QYWX_IN_SESSION_VALIDITY, userId, authState.getClientType());
                redissonService.set(cacheKey, bjwt, userAuthCfgBean.getSessionMaxInterval() * 1000L);

                if (authState.getWriteCookie()) {
                    // 如果是后端授权，需要将bjwt写到Cookie中
                    // 创建一个Cookie对象
                    Cookie cookie = new Cookie("bjwt", bjwt);
                    // 设置Cookie的属性（可选）
                    cookie.setMaxAge(userAuthCfgBean.getBaseAuthExpire() * 24 * 3600); // 设置Cookie的有效期
                    cookie.setPath("/"); // 设置Cookie的路径
                    // 将Cookie添加到响应中
                    response.addCookie(cookie);
                }
            }

            // 重定向到错误页面
            if (StringUtils.isBlank(redirectUrl)) {
                log.warn("自建应用授权，redirectUrl为空，authDto = {}", JSON.toJSONString(authDto));
                ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.UNKNOWN_ERROR_CODE, response);
                return;
            }
            // 4.根据情况，重定向到自建应用授权地址进行授权
            // 或者重定向到跳转地址并携带token
            ResponseUtils.sendRedirect(response, userAuthCfgBean.getSafeHosts(), redirectUrl);
        } catch (Exception e) {
            log.error("自建应用授权异常，异常信息：{}", e.getMessage(), e);
            ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.UNKNOWN_ERROR_CODE, response);
        }
    }

    /**
     * 封装返回URL
     *
     * @param authState
     * @return
     * @throws UnsupportedEncodingException
     */
    public String getSelfAppAuthUrl(WXAuthState authState) throws UnsupportedEncodingException {
        String scope = null;
        String authType = authState.getAuthType();
        if (UserAuthConstant.SELF_APP_AUTH_TYPE_BASE.equals(authType) || StringUtils.isEmpty(authType)) {
            scope = SCOPE_BASE;
        } else if (UserAuthConstant.SELF_APP_AUTH_TYPE_USER_INFO.equals(authType)) {
            scope = SCOPE_PRIVATE;
        } else {
            return null;
        }

        return new StringBuilder().append(userAuthCfgBean.getWxAuthUrl())
                .append("appid=").append(userAuthCfgBean.getCorpId())
                .append("&redirect_uri=").append(URLEncoder.encode(userAuthCfgBean.getRedirectUrl(), "UTF-8"))
                .append("&response_type=code")
                .append("&scope=").append(scope)
                .append("&state=").append(authState.getState())
                .append("&agentid=").append(userAuthCfgBean.getAgentId())
                .append("#wechat_redirect")
                .toString();
    }

    /**
     * 获取 access_token
     * 根据不同的应用获取对应的access_token
     * 详情见：https://work.weixin.qq.com/api/doc/90000/90135/91039
     *
     * @return
     */
    private String getAccessToken(String corpId) {
        String accessToken = getCurCorpAccessToken();
        if (StringUtils.isNotEmpty(corpId) && !StringUtils.equals(corpId, userAuthCfgBean.getCorpId())) {
            //1、先通过下游corpId 过去agentId
            String agentId = getAgendIdByCorp(corpId);
            //2、获取下游的accessToken
            return getCorpGroupAccessToken(accessToken, corpId, agentId);
        }
        return accessToken;
    }

    /**
     * 获取当前企业 access_token
     * 根据不同的应用获取对应的access_token
     * 详情见：https://work.weixin.qq.com/api/doc/90000/90135/91039
     *
     * @return
     */
    private String getCurCorpAccessToken() {
        String accessToken = StringUtils.EMPTY;
        // 先去缓存里面查询有没有access_token
        String accessTokenKey = String.format(CacheConstans.ACCESS_TOKEN_KEY, userAuthCfgBean.getCorpId(), userAuthCfgBean.getSecretKey());
        String accessTokenRedisResult = redissonService.getStr(accessTokenKey);
        if (StringUtils.isNotBlank(accessTokenRedisResult)) {
            accessToken = accessTokenRedisResult;
            return accessToken;
        }

        // 加锁，保证只能有一个线程去获取企业微信的access_token
        String lockName = userAuthCfgBean.getCorpId() + userAuthCfgBean.getSecretKey();
        try {
            /**
             * 锁粒度：全局锁，针对机构生效
             * 锁持有时间：3s
             * 锁等待时间：3s
             */
            if (redissonService.tryLock(lockName, CacheConstans.ACCESS_TOKEN_LOCK_TIME, CacheConstans.ACCESS_TOKEN_LOCK_TIME)) {
                // 再去缓存里判断一次有没有access_token
                accessTokenRedisResult = redissonService.getStr(accessTokenKey);
                if (StringUtils.isNotBlank(accessTokenRedisResult)) {
                    accessToken = accessTokenRedisResult;
                    return accessToken;
                }

                Map<String, Object> param = new HashMap<>();
                param.put("corpid", userAuthCfgBean.getCorpId());
                param.put("corpsecret", userAuthCfgBean.getSecretKey());
                String result = httpService.get(userAuthCfgBean.getAccessTokenUrl(), param);

                JSONObject jsonObject = JSON.parseObject(result);
                if (jsonObject.getIntValue("errcode") == BusinessConstants.SUCCESS_CODE) {
                    // 放入redis
                    long time = jsonObject.getLong("expires_in") * 1000;
                    String value = jsonObject.getString("access_token");
                    redissonService.set(accessTokenKey, value, time);
                    accessToken = value;
                }
            }
        } catch (Exception e) {
            log.info("corpId={},secretKey={},调用企业微信获取access_token出错，信息是:{}", userAuthCfgBean.getCorpId(), userAuthCfgBean.getSecretKey(), e);
        } finally {
            redissonService.unlock(lockName);
        }
        return accessToken;
    }

    /**
     * 获取当前上下游的accessToken
     * 根据不同的应用获取对应的access_token
     *
     * @return
     */
    private String getCorpGroupAccessToken(String parentAccessToken, String corpId, String agentId) {
        String accessToken = StringUtils.EMPTY;
        // 先去缓存里面查询有没有access_token
        String accessTokenKey = String.format(CacheConstans.CORP_GEOUP_ACCESS_TOKEN_KEY, corpId, agentId);
        String accessTokenRedisResult = redissonService.getStr(accessTokenKey);
        if (StringUtils.isNotBlank(accessTokenRedisResult)) {
            accessToken = accessTokenRedisResult;
            return accessToken;
        }

        // 加锁，保证只能有一个线程去获取企业微信的access_token
        String lockName = String.format(CacheConstans.CORP_GEOUP_ACCESS_TOKEN_LOCK, corpId, agentId);
        try {
            /**
             * 锁粒度：全局锁，针对机构生效
             * 锁持有时间：3s
             * 锁等待时间：3s
             */
            if (redissonService.tryLock(lockName, CacheConstans.ACCESS_TOKEN_LOCK_TIME, CacheConstans.ACCESS_TOKEN_LOCK_TIME)) {
                // 再去缓存里判断一次有没有access_token
                accessTokenRedisResult = redissonService.getStr(accessTokenKey);
                if (StringUtils.isNotBlank(accessTokenRedisResult)) {
                    accessToken = accessTokenRedisResult;
                    return accessToken;
                }

                Map<String, Object> param = new HashMap<>();
                param.put("corpid", corpId);
                param.put("agentid", agentId);
                param.put("business_type", 1);
                String url = userAuthCfgBean.getCorpGroupAccessTokenUrl() + parentAccessToken;
                String result = httpService.post(url, JSON.toJSONString(param));
                log.info("getCorpGroupAccessToken url:{},corpId={},agentId={},result:{}", url, corpId, agentId, result);
                JSONObject jsonObject = JSON.parseObject(result);
                if (jsonObject.getIntValue("errcode") == BusinessConstants.SUCCESS_CODE) {
                    // 放入redis
                    long time = jsonObject.getLong("expires_in") * 1000;
                    String value = jsonObject.getString("access_token");
                    redissonService.set(accessTokenKey, value, time);
                    accessToken = value;
                    log.info("调用企业微信获取下游的accessToken corpId={},agentId={},accessToken:{}", corpId, agentId, accessToken);
                }
            }
        } catch (Exception e) {
            log.info("corpId={},agentId={},调用企业微信获取下游的accessToken出错，信息是:{}", corpId, agentId, e);
        } finally {
            redissonService.unlock(lockName);
        }
        return accessToken;
    }


    /**
     * 获取ticket
     *
     * @param type agent/config
     * @return
     */
    private String getJsapiTicket(String corpId, String type) {
        String jsTicketJsonResult = StringUtils.EMPTY;
        // 先去缓存里面查询有没有access_token
        String jsTicketKey;
        String curCorpId = corpId == null ? userAuthCfgBean.getCorpId() : corpId;
        if (CacheConstans.AGENT.equals(type)) {
            jsTicketKey = String.format(CacheConstans.JSAPI_TICKET_AGENT_KEY, curCorpId, type);
        } else {
            jsTicketKey = String.format(CacheConstans.JSAPI_TICKET_KEY, curCorpId);
        }
        String jsTicketJson = redissonService.getStr(jsTicketKey);
        if (StringUtils.isNotBlank(jsTicketJson)) {
            return jsTicketJson;
        }
        // 加锁，保证只能有一个线程去获取企业微信的access_token
        String lockName;
        if (CacheConstans.AGENT.equals(type)) {
            lockName = curCorpId + type;
        } else {
            lockName = curCorpId;
        }
        try {
            /**
             * 锁粒度：全局锁，针对机构生效
             * 锁持有时间：3s
             * 锁等待时间：3s
             */
            if (redissonService.tryLock(lockName, CacheConstans.JSAPI_TICKET_LOCK_TIME, CacheConstans.ACCESS_TOKEN_LOCK_TIME)) {
                // 再去缓存里判断一次有没有access_token
                jsTicketJsonResult = redissonService.getStr(jsTicketKey);
                if (StringUtils.isNotBlank(jsTicketJsonResult)) {
                    return jsTicketJsonResult;
                }

                String httpUrl;
                Map<String, Object> param = new HashMap<>();
                String accessToken = this.getAccessToken(corpId);
                param.put("access_token", accessToken);

                if (CacheConstans.AGENT.equals(type)) {
                    param.put("type", "agent_config");
                    httpUrl = userAuthCfgBean.getGetAgentTicketUrl();
                } else {
                    httpUrl = userAuthCfgBean.getGetJsapiTicketUrl();
                }
                String result = httpService.get(httpUrl, param);
                JSONObject jsonObject = JSON.parseObject(result);
                if (jsonObject.getIntValue("errcode") == BusinessConstants.SUCCESS_CODE) {
                    // 放入redis
                    long time = jsonObject.getLong("expires_in") * 1000;
                    jsTicketJsonResult = jsonObject.getString("ticket");
                    redissonService.set(jsTicketKey, jsTicketJsonResult, time);
                }
            }
        } catch (Exception e) {
            log.warn("corpId={}，secretKey={}调用企业微信获取ticket出错，信息是：{}", userAuthCfgBean.getCorpId(), userAuthCfgBean.getSecretKey(), e);
        } finally {
            redissonService.unlock(lockName);
        }
        return jsTicketJsonResult;
    }

    /**
     * 基础授权
     *
     * @param userId
     * @param authAccount
     * @param bjwte
     * @param hasPrivate
     * @return
     */
    private boolean invokeBaseAuth(String userId, Map<String, String> authAccount, AtomicLong bjwte, AtomicInteger hasPrivate) {
        String appId = userAuthCfgBean.getCorpId();
        SelfappAuthAcc authAcc = querySelfappAuthAcc(appId, userId);

        long ct = Long.parseLong(authAccount.get(CT));
        if (authAcc == null) {
            // 首次授权，保存入库
            authAcc = new SelfappAuthAcc().setAppId(appId).setUserId(userId);
            AddSelfAppAuthAccResp addResp = rpcAuthAccService.addSelfAppAuthAcc(new AddSelfAppAuthAccReq().setSelfAppAuthAcc(authAcc));
            if (addResp == null || addResp.getRetCodeInt() != BusinessConstants.SUCCESS_CODE) {
                log.error("自建应用授权，新增授权账户信息异常，RPC接口返回：{}", JSON.toJSONString(addResp));
                return false;
            }
            bjwte.set(LocalDateTime.ofInstant(Instant.ofEpochMilli(ct), ZoneId.systemDefault()).plusDays(userAuthCfgBean.getBaseAuthExpire()).toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
            log.info("完成自建应用授权，用户信息为：{}", JSON.toJSONString(authAccount));
        } else {
            // 已经完成过授权
            LocalDateTime lastAdvancedTime = authAcc.getLastAdvancedTime();

            // 判断是否完成过高级授权，且高级授权在有效期内
            if (authAcc.getAdvancedAuthFlag() == UserAuthConstant.ADVANCED_AUTH_FLAG
                    && lastAdvancedTime.plusDays(userAuthCfgBean.getUerInfoAuthExpire()).isAfter(LocalDateTime.now())) {
                bjwte.set(lastAdvancedTime.plusDays(userAuthCfgBean.getUerInfoAuthExpire()).toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
                hasPrivate.set(UserAuthConstant.ADVANCED_AUTH_FLAG);
                log.info("已完成高级授权且在有效期内，返回高级授权用户信息为：{}", JSON.toJSONString(authAccount));
            } else {
                bjwte.set(LocalDateTime.ofInstant(Instant.ofEpochMilli(ct), ZoneId.systemDefault()).plusDays(userAuthCfgBean.getBaseAuthExpire()).toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
                log.info("未完成高级授权或高级授权已过期，返回基础授权用户信息为：{}", JSON.toJSONString(authAccount));
            }
        }

        return true;
    }

    /**
     * 高级授权
     *
     * @param userId
     * @param userTicket
     * @param authAccount
     * @param bjwte
     * @return
     */
    private boolean invokeUserInfoAuth(String userId, String userTicket, Map<String, String> authAccount, AtomicLong bjwte) {
        GetUserDetailDto userDetail = getUserDetail(userTicket, false);
        if (userDetail == null) {
            log.error("自建应用授权，获取用户敏感信息为null");
            return false;
        }

        String appId = userAuthCfgBean.getCorpId();
        SelfappAuthAcc authAcc = querySelfappAuthAcc(appId, userId);
        if (authAcc == null) {
            authAcc = new SelfappAuthAcc();
        }

        authAcc.setAppId(appId);
        authAcc.setUserId(userId);
        authAcc.setGender(userDetail.getGender());
        String avatar = userDetail.getAvatar();
        authAcc.setAvatar(avatar);
        authAcc.setQrCode(userDetail.getQrCode());
        authAcc.setMobile(userDetail.getMobile());
        authAcc.setEmail(userDetail.getEmail());
        authAcc.setBizMail(userDetail.getBizMail());
        authAcc.setAddress(userDetail.getAddress());
        authAcc.setAdvancedAuthFlag(UserAuthConstant.ADVANCED_AUTH_FLAG);
        authAcc.setLastAdvancedTime(LocalDateTime.now());

        /**
         * 处理头像
         */
        if (userAuthCfgBean.isUserAvatarDownload()) {
            String hostMapping = userAuthCfgBean.getAvatarUrlHostMapping();
            if (StringUtils.isNotEmpty(hostMapping)) {
                String prefix = getUrlPrefix(avatar);
                JSONObject hostMappingObj = JSON.parseObject(hostMapping);
                String newPrefix = hostMappingObj.getString(prefix);
                if (StringUtils.isNotEmpty(newPrefix)) {
                    avatar = avatar.replaceFirst(prefix, newPrefix);
                }
            }

            String imageBase64 = httpService.imageUrlToBase64(avatar);
            authAcc.setAvatarBase64(imageBase64);
        }

        try {
            if (authAcc.getId() == null) {
                AddSelfAppAuthAccResp addResp = rpcAuthAccService.addSelfAppAuthAcc(new AddSelfAppAuthAccReq().setSelfAppAuthAcc(authAcc));
                if (addResp == null || !addResp.isSucc()) {
                    log.warn("自建应用授权，保存授权信息失败，{}", JSON.toJSONString(addResp));
                }
            } else {
                UpdateSelfAppAuthAccResp updateResp = rpcAuthAccService.updateSelfAppAuthAcc(new UpdateSelfAppAuthAccReq().setSelfAppAuthAcc(authAcc));
                if (updateResp == null || !updateResp.isSucc()) {
                    log.warn("自建应用授权，更新授权信息失败，{}", JSON.toJSONString(updateResp));
                }
            }
        } catch (Exception e) {
            log.warn("自建应用授权，授权信息新增或更新异常，异常信息：{}", e.getMessage(), e);
        }

        bjwte.set(LocalDateTime.now().plusDays(userAuthCfgBean.getUerInfoAuthExpire()).toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
        log.info("自建应用授权，返回高级授权用户信息为：{}", JSON.toJSONString(authAccount));

        return true;
    }

    /**
     * 查询自建应用授权用户信息
     *
     * @param appId
     * @param userId
     * @return
     */
    private SelfappAuthAcc querySelfappAuthAcc(String appId, String userId) {
        String cacheKey = String.format(CacheConstans.SELF_APP_AUTH_ACC_CAHCE, appId, userId);
        SelfappAuthAcc selfappAuthAcc = redissonService.get(cacheKey, SelfappAuthAcc.class);
        if (selfappAuthAcc == null) {
            QuerySelfAppAuthAccResp queryResp = rpcAuthAccService.querySelfAppAuthAcc(new QuerySelfAppAuthAccReq().setAppId(appId).setUserId(userId));
            if (queryResp == null || queryResp.getRetCodeInt() != BusinessConstants.SUCCESS_CODE) {
                log.error("自建应用授权，查询授权账户信息异常，RPC接口返回：{}", JSON.toJSONString(queryResp));
                return null;
            } else {
                selfappAuthAcc = queryResp.getSelfAppAuthAcc();
                if (selfappAuthAcc != null) {
                    redissonService.setJson(cacheKey, selfappAuthAcc, TimeUnit.HOURS.toMillis(2));
                }
            }
        }

        return selfappAuthAcc;
    }

    /**
     * https://work.weixin.qq.com/api/doc/90000/90136/90506
     * 企业的绑定到config
     * 应用的绑定到agent
     *
     * @param url
     * @param corpId
     * @return
     */
    @Override
    public BetaSdkDto getBetaSdkDto(String url, String corpId) {
        String agentId = userAuthCfgBean.getAgentId();
        if (!StringUtils.isEmpty(corpId)) {
            agentId = getAgendIdByCorp(corpId);
            log.warn("上下游中对应corpId:{},agentId:{}", corpId, agentId);
        } else {
            corpId = userAuthCfgBean.getCorpId();
        }
        long timestamp = System.currentTimeMillis() / 1000;
        String nonce = WXUtils.getNonceString(8);
        // 获取config签名
        String signature = this.generateSignature(corpId, timestamp, nonce, url, CacheConstans.CONFIG);

        BetaSdkDto betaSdkDto = new BetaSdkDto();
        SdkConfig sdkConfig = SdkConfig.builder().beta(true).appId(corpId).nonceStr(nonce)
                .timestamp(timestamp).signature(signature).build();
        // 获取agent签名
        signature = this.generateSignature(corpId, timestamp, nonce, url, CacheConstans.AGENT);

        SdkAgent sdkAgent = SdkAgent.builder().agentid(Integer.parseInt(agentId))
                .corpid(corpId).nonceStr(nonce).timestamp(timestamp).signature(signature).build();

        betaSdkDto.setAgent(sdkAgent);
        betaSdkDto.setConfig(sdkConfig);
        betaSdkDto.setPrivacy(false);
        return betaSdkDto;
    }

    @Override
    public GetUserDetailDto getUserDetail(String userTicket, boolean again) {
        // 获取access_token
        String accessToken = getAccessToken(null);
        log.info("获取访问用户敏感信息的access_token={}, userTicket={}", accessToken, userTicket);
        String url = userAuthCfgBean.getUserDetailUrl() + "?access_token=" + accessToken;
        Map<String, Object> body = new HashMap<>(1);
        body.put("user_ticket", userTicket);
        String result = httpService.post(url, JSON.toJSONString(body));
        log.info("获取访问用户敏感信息得到的数据result：{}, userTicket={}", result, userTicket);
        GetUserDetailDto userDetailDTO = JSON.parseObject(result, GetUserDetailDto.class);
        if (Objects.isNull(userDetailDTO) || userDetailDTO.getErrcode().intValue() != 0) {
            if (!again) {
                // 重试一次
                getUserDetail(userTicket, true);
            }
            log.warn("获取访问用户敏感信息返回异常，access_token={}, userTicket={}, result={}", accessToken, userTicket, result);
            return null;
        }

        return userDetailDTO;
    }

    /**
     * 上下游中通过corpId获取对应的agentId
     *
     * @param corpId
     * @return
     */
    private String getAgendIdByCorp(String corpId) {
        if (StringUtils.isEmpty(corpId)) {
            return StringUtils.EMPTY;
        }
        AuthCorpInfoResp authCorpInfoResp = authCorpInfoervice.queryByCorp(corpId);
        if (null == authCorpInfoResp || StringUtils.isEmpty(authCorpInfoResp.getAgentId())) {
            log.error("通过下游的corpId获取下游agentId 失败，请检查配置，corpId:{}", corpId);
        }
        return authCorpInfoResp.getAgentId();
    }

    /**
     * 获取签名
     *
     * @param timestamp
     * @param nonce
     * @param url
     * @param type
     * @return
     */
    private String generateSignature(String corpId, long timestamp, String nonce, String url, String type) {
        String signature = "";
        // 获取ticket
        String ticket = this.getJsapiTicket(corpId, type);
        // 替换占位符
        String signatureStr = String.format(BusinessConstants.JSAPI_SIGNATURE, ticket, nonce, timestamp, url);
        try {
            signature = SHA1.shaEncode(signatureStr).replaceAll("-", "").toLowerCase();
        } catch (Exception e) {
            log.error("SHA1 加密异常，type={}, 加密前signatureStr={}, 加密后signature={}， 异常信息={}", type, signatureStr, signature, e.getMessage(), e);
            return null;
        }
        return signature;
    }

    /**
     * 获取访问用户身份
     * 详情见：https://work.weixin.qq.com/api/doc/90000/90135/91707
     */
    @Override
    public GetUserInfoDto getUserInfo(String code, boolean again) {
        // 获取access_token
        String accessToken = getAccessToken(null);
        Map<String, Object> param = new HashMap<>();
        param.put("access_token", accessToken);
        param.put("code", code);
        String result = httpService.get(userAuthCfgBean.getUserInfoUrl(), param);

        GetUserInfoDto userInfoDTO = JSON.parseObject(result, GetUserInfoDto.class);
        if (Objects.isNull(userInfoDTO) || userInfoDTO.getErrcode().intValue() != 0) {
            if (!again) {
                // 重试一次
                getUserInfo(code, true);
            }
            log.error("获取访问用户身份得到的数据result：{}, code={}, access_token={}", result, code, accessToken);
            return null;
        }
        // 判断返回是否有userId,如果存在将userId转换为openId
        if (!Objects.isNull(userInfoDTO.getUserId()) && userAuthCfgBean.isEnableOpenId()) {
            Map<String, Object> userParam = new HashMap<>();
            userParam.put("userid", userInfoDTO.getUserId());
            String preUrl = userAuthCfgBean.getUserIdConvertOpenIdUrl();
            String ret = httpService.post(preUrl + accessToken, JSON.toJSONString(userParam));
            JSONObject object = JSON.parseObject(ret);
            if (!Objects.isNull(object) && object.containsKey(OPEN_ID) && !Objects.isNull(object.get(OPEN_ID))) {
                userInfoDTO.setOpenId(object.get(OPEN_ID).toString());
            }
        }
        return userInfoDTO;
    }

    private static String getUrlPrefix(String urlString) {
        try {
            URL url = new URL(urlString);
            // 获取协议
            String protocol = url.getProtocol();
            // 获取主机
            String host = url.getHost();
            // 获取端口（如果没有明确指定端口，则返回-1）
            int port = url.getPort();

            // 构建前半部分字符串
            StringBuilder prefixBuilder = new StringBuilder();
            prefixBuilder.append(protocol).append("://").append(host);
            if (port != -1) {
                prefixBuilder.append(":").append(port);
            }

            return prefixBuilder.toString();
        } catch (MalformedURLException e) {
            // URL格式错误
            return null;
        }
    }

}